Skip to content
created by Aha00aAha00a at 2026-05-24
last modified by Aha00aAha00a at 2026-08-10
revision: 3

Dev Attachment

1. 개요

파일을 S3에 업로드하고, 위키 페이지에서 [[Attachment(...)]] 매크로로 첨부파일을 삽입하는 기능.

2. 매크로 사용법

[[Attachment(파일명)]]
[[Attachment(파일명, 300px)]]
[[Attachment(파일명, 50%)]]
  • 이미지 확장자(png, jpg, jpeg, gif, webp, svg, bmp, avif, tiff, tif, ico)면 <img> 태그로 인라인 표시
  • 그 외 확장자면 <a> 다운로드 링크로 표시
  • 두 번째 인자로 너비 지정 가능 (px 또는 % 단위)
  • 이미지 로드 실패 시 fallback 이미지(attachmentFallback.svg) 표시

3. S3 오브젝트 키 구조

Attachment/{siteSeq}/{pageName}/{originalFilename}/{basename}.{timestamp}.{ext}

예시:

Attachment/1/MyPage/photo.jpg/photo.20240101120000000.jpg
Attachment/1/MyPage/clipboard/clipboard.20240101120000000.png
  • pageName과 파일명의 특수문자는 [^\p{IsHangul}\p{IsHan}\p{IsHiragana}\p{IsKatakana}a-zA-Z0-9._-] 패턴으로 _ 치환
  • 클립보드 이미지는 {pageName}/clipboard/ 하위에 저장

이 레이아웃은 logics.AttachmentLogic 한 곳에서만 조립한다. Root, sitePrefix, pagePrefix, sanitizePathSegment 가 그 자리다.

키를 쓰는 쪽과 읽는 쪽이 프리픽스를 각자 조립하면, 어긋나도 컴파일은 통과하고 **첨부가 사라진 뒤에야** 드러난다. 실제로 치환 함수가 Api·ApiV1·Wiki·MacroAttachment 에 네 벌 있었다.

4. presigned URL

  • 오브젝트에 접근 시 AWS S3 presigned URL 생성 (유효기간 24시간)
  • 매크로 렌더링마다 URL이 새로 생성됨

5. DB 스키마 (Attachment 테이블)

컬럼

설명

seq

PK

site

사이트 seq

pageName

첨부된 페이지 이름

user

업로드 사용자 seq (nullable)

uploaderEmail

업로드자 이메일 (nullable)

originalFilename

원래 파일명

storedFilename

S3에 저장된 파일명

bucket

S3 버킷 이름

objectKey

S3 오브젝트 키

contentType

MIME 타입

fileSize

파일 크기 (bytes)

status

Initiated / Uploaded / Failed / Deleted

etag

S3 ETag (업로드 후 저장)

dateInserted

레코드 생성 일시

dateUploaded

S3 업로드 완료 일시

dateUpdated

최근 변경 일시

dateDeleted

삭제 일시 (soft delete)

5.1. status 흐름

Initiated → Uploaded (S3 업로드 성공)
          → Failed   (S3 업로드 실패)
Uploaded  → Deleted  (삭제 처리)

6. API 엔드포인트

6.1. 파일 업로드

POST /api/uploadAttachment
Content-Type: multipart/form-data

파라미터:
  pageName (text)  - 첨부할 페이지 이름
  file     (file)  - 업로드할 파일

응답 (JSON):
  objectKey        - S3 오브젝트 키
  attachmentMacro  - 바로 사용 가능한 매크로 문자열 예: [[Attachment(파일명)]]
  fileUrl          - presigned URL (24시간 유효)
  contentType      - MIME 타입

6.2. 페이지 첨부파일 목록

GET /api/pageAttachments?pageName={pageName}

응답 (JSON):
  attachments: [
    {
      objectKey       - S3 오브젝트 키
      originalFilename
      contentType
      fileSize
      fileUrl         - presigned URL
      integrityStatus - OK / DB_ONLY / S3_ONLY
      attachmentMacro - 매크로 문자열
    }
  ]
  • DB 레코드와 S3 오브젝트를 함께 조회하여 정합성 상태(integrityStatus) 제공

6.3. 첨부파일 삭제

POST /api/deleteAttachment
Content-Type: application/x-www-form-urlencoded

파라미터:
  pageName  - 페이지 이름
  objectKey - S3 오브젝트 키 (전체 경로 또는 상대 경로)

응답 (JSON):
  ok        - true
  objectKey - 삭제된 오브젝트 키
  • S3에서 실제 삭제 후 DB에 soft delete (status='Deleted', dateDeleted=NOW())

6.4. 클립보드 이미지 업로드

POST /api/uploadClipboardImage
Content-Type: multipart/form-data

파라미터:
  pageName - 페이지 이름
  file     - 이미지 파일 (Content-Type이 image/* 여야 함)

응답 (JSON):
  objectKey
  attachmentMacro
  imageUrl
  • 과거에는 base64 dataUrl을 urlencoded form으로 받았으나, base64/URL 인코딩으로 요청 크기가 부풀어 play.http.parser.maxMemoryBuffer(2MB) 초과 시 413이 발생하여 multipart로 변경

7. 페이지 삭제 시 동작

  • 페이지 삭제 시 해당 페이지의 첨부파일도 S3 및 DB에서 함께 삭제 (cascade)
  • S3는 Attachment/{siteSeq}/{pageName}/ 프리픽스로 최대 200개 조회하여 삭제
  • S3 오브젝트를 모두 지운 뒤에야 DB 행을 삭제 표시한다. 순서를 뒤집으면 삭제가 실패했을 때 아무도 가리키지 않는 오브젝트가 남는다.
  • 구현은 AttachmentLogic.deletePageAttachments 하나다.

7.1. S3 미설정일 때

AWS_REGION·AWS_ACCESS_KEY_ID·AWS_SECRET_ACCESS_KEY·bucket 중 하나라도 비어 있으면 S3 작업을 건너뛴다. 첨부는 "없음"으로 취급하고 DB 정리는 그대로 진행한다.

이 가드는 원래 ApiV1 에만 있었다. 같은 함수가 Wiki 에도 한 벌 더 있었고 그쪽에는 가드가 없어서, S3 미설정 상태에서는 빈 자격증명·빈 리전으로 클라이언트를 만들어 호출했다. 사본이 둘이면 한쪽만 고쳐도 컴파일이 통과한다는 것의 실례다. 합치면서 가드 있는 쪽으로 통일했다.

8. S3 클라이언트

logics.S3Logic 이 자격증명별로 캐시해 돌려준다.

AmazonS3 는 커넥션 풀을 들고 있고 공유하도록 만들어진 객체다. 컨트롤러 3곳이 각자 buildAmazonS3Client() 를 갖고 **요청마다 새로 만들고 있었다**(호출부 11곳). 만든 클라이언트를 닫는 곳은 없었으므로 풀이 요청마다 쌓였다. 어디서도 클라이언트를 닫거나 상태를 바꾸지 않으므로 자격증명당 한 개를 공유해도 안전하다.

9. 설계 결정사항

  • 저장소: S3 presigned URL 방식 (직접 S3 URL 노출 없음)
  • 종속 방식: 페이지에 종속 (Attachment/{siteSeq}/{pageName}/ 경로)
  • 파일 목록: 페이지 편집 시 해당 페이지 첨부파일 목록 표시 가능
  • 파일명 중복: 타임스탬프를 파일명에 포함하여 덮어쓰기 방지
  • referer 체크 없음, presigned URL의 만료(24시간)로 접근 제어

10. See Also

10.2. Similar Pages

Similar pages by cosine similarity. Words after page name are term frequency.

  • Same Wiki
    • 41.97% MacroAttachment attachment(23:9), s3(25:1), 파일명(7:6), url(11:1), presigned(7:1), seq(6:2), 이미지(6:2), 파일(6:1), site(5:2), clipboard(4:2)
    • 37.02% Dev Page page(16:21), attachment(23:2), url(11:6), 페이지(11:5), name(10:6), 삭제(11:1), content(7:2), seq(6:3), site(5:4), api(7:1)
  • Sister Wikis
    • 38.40% Aha00a:Aws S3 s3(25:25), url(11:18), attachment(23:1), aws(2:14), name(10:1), file(7:4), key(7:4), content(7:3), object(6:2), json(4:4)

10.3. Adjacent Pages

Control
≤ 32
all
1.0x
1.0x
80
-120
ON
Metrics
Nodes(visible/total)0/0
Links(visible/total)0/0
Avg degree0.00
Depth coverage0
Queue(fetch/graph)0 / 0
Zoom(scale)1.00x
Ctrl/⌘ + Scroll: Zoom
Root 1-hop 2-hop+